#include "dice_renderer.h"

#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>

#include <qrender/camera.h>

DiceRenderer::DiceRenderer()
{
}

DiceRenderer::~DiceRenderer()
{
    DiceRenderer::release();
}

void DiceRenderer::create()
{
    // 创建着色器
    const char *vsrc = R"(#version 330
in vec3 v_pos;
in vec2 v_texture_coord;
out vec2 texture_coord;

uniform mat4 view_matrix;
uniform mat4 projection_matrix;
uniform mat4 model_matrix;

void main() {
   gl_Position = projection_matrix * view_matrix * model_matrix * vec4(v_pos, 1.0f);
   texture_coord = v_texture_coord;
}
)";
    const char *fsrc = R"(
#version 330 core

in vec2 texture_coord;
out vec4 frag_color;

uniform sampler2D texture_id;

void main() {
   frag_color = texture(texture_id, texture_coord);
}
)";

    program_ = std::make_shared<QOpenGLShaderProgram>();
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,
                                               vsrc); // 顶点着色器
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,
                                               fsrc); // 片段着色器
    program_->link();
    program_->bind();

    program_->setUniformValue("view_matrix", QMatrix4x4());
    program_->setUniformValue("projection_matrix", QMatrix4x4());

    vao_ = std::make_shared<QOpenGLVertexArrayObject>();
    vao_->create();

    // 顶点数据
    GLfloat v[] = {
        // positions          // texture coords
        -300.0f, -300.0f, 300.0f,  0.0f, 0.0f, //
        -300.0f, 300.0f,  300.0f,  0.0f, 1.0f, //
        300.0f,  300.0f,  300.0f,  1.0f, 1.0f, //
        300.0f,  -300.0f, 300.0f,  1.0f, 0.0f, //
        -300.0f, -300.0f, 300.0f,  0.0f, 0.0f, //

        -300.0f, -300.0f, -300.0f, 0.0f, 0.0f, //
        -300.0f, 300.0f,  -300.0f, 0.0f, 1.0f, //
        300.0f,  300.0f,  -300.0f, 1.0f, 1.0f, //
        300.0f,  -300.0f, -300.0f, 1.0f, 0.0f, //
        -300.0f, -300.0f, -300.0f, 0.0f, 0.0f, //

        -300.0f, -300.0f, 300.0f,  0.0f, 0.0f, //
        -300.0f, 300.0f,  300.0f,  0.0f, 1.0f, //
        -300.0f, 300.0f,  -300.0f, 1.0f, 1.0f, //
        -300.0f, -300.0f, -300.0f, 1.0f, 0.0f, //
        -300.0f, -300.0f, 300.0f,  0.0f, 0.0f, //

        300.0f,  -300.0f, 300.0f,  0.0f, 0.0f, //
        300.0f,  300.0f,  300.0f,  0.0f, 1.0f, //
        300.0f,  300.0f,  -300.0f, 1.0f, 1.0f, //
        300.0f,  -300.0f, -300.0f, 1.0f, 0.0f, //
        300.0f,  -300.0f, 300.0f,  0.0f, 0.0f, //

        -300.0f, 300.0f,  300.0f,  0.0f, 0.0f, //
        -300.0f, 300.0f,  -300.0f, 0.0f, 1.0f, //
        300.0f,  300.0f,  -300.0f, 1.0f, 1.0f, //
        300.0f,  300.0f,  300.0f,  1.0f, 0.0f, //
        -300.0f, 300.0f,  300.0f,  0.0f, 0.0f, //

        -300.0f, -300.0f, 300.0f,  0.0f, 0.0f, //
        -300.0f, -300.0f, -300.0f, 0.0f, 1.0f, //
        300.0f,  -300.0f, -300.0f, 1.0f, 1.0f, //
        300.0f,  -300.0f, 300.0f,  1.0f, 0.0f, //
        -300.0f, -300.0f, 300.0f,  0.0f, 0.0f  //
    };
    vbo_ = std::make_shared<QOpenGLBuffer>();
    vbo_->create();
    vbo_->bind();
    vbo_->allocate(v, sizeof(GLfloat) * 5 * 30);

    {
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());

        //告知OPenGL如何解析数据, 数据解析格式
        GLuint vPosition = program_->attributeLocation("v_pos");
        program_->setAttributeBuffer(vPosition, GL_FLOAT, 0, 3,
                                     5 * sizeof(GLfloat));
        program_->enableAttributeArray(vPosition);

        GLuint vColor = program_->attributeLocation("v_texture_coord");
        program_->setAttributeBuffer(vColor, GL_FLOAT, 3 * sizeof(GLfloat), 2,
                                     5 * sizeof(GLfloat));
        program_->enableAttributeArray(vColor);
    }

    for (int i = 0; i < 6; i++) {
        auto texture = std::make_shared<QOpenGLTexture>(
            QImage(QString(":/images/side%1.png").arg(i + 1)).mirrored());
        texture->setWrapMode(QOpenGLTexture::DirectionS,
                             QOpenGLTexture::Repeat);
        texture->setWrapMode(QOpenGLTexture::DirectionT,
                             QOpenGLTexture::Repeat);
        texture->setMinificationFilter(QOpenGLTexture::Linear);
        texture->setMagnificationFilter(QOpenGLTexture::Linear);
        textures_ << texture;
    }

    cube_positions_ = { QVector3D(0.0f, 0.0f, 0.0f),
                        QVector3D(200.0f, 500.0f, -1500.0f),
                        QVector3D(-150.0f, -220.0f, -250.0f),
                        QVector3D(-380.0f, -200.0f, -1230.0f),
                        QVector3D(240.0f, -40.0f, -350.0f),
                        QVector3D(-170.0f, 300.0f, -750.0f),
                        QVector3D(130.0f, -200.0f, -250.0f),
                        QVector3D(150.0f, 200.0f, -250.0f),
                        QVector3D(150.0f, 20.0f, -150.0f),
                        QVector3D(-130.0f, 100.0f, -150.0f) };

    program_->release();

    vbo_->release();
}

void DiceRenderer::render(const CameraInfo &camera_info)
{
    program_->bind();

    if (camera_info.valid) {
        program_->setUniformValue("view_matrix", camera_info.view_matrix);
        program_->setUniformValue("projection_matrix",
                                  camera_info.projection_matrix);
    }

    static int time_ = 0;                              // 定时旋转
    for (int i = 0; i < cube_positions_.size(); i++) { // 绘制多个骰子
        QMatrix4x4 model_matrix;
        model_matrix.translate(cube_positions_.at(i));
        model_matrix.rotate(
            (i + 1.0f) * time_,
            QVector3D(1.0f, 0.3f, 0.5f)); // 每个骰子旋转速度不一样
        program_->setUniformValue("model_matrix", model_matrix);

        for (int j = 0; j < 6; j++) { // 绘制骰子的六个面
            // 加载纹理
            program_->setUniformValue("texture_id", j);
            textures_.at(j)->bind(j);

            // 绘制
            QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());
            QOpenGLContext::currentContext()->functions()->glDrawArrays(
                GL_TRIANGLE_STRIP, j * 5, 5);
        }
    }
    time_++;

    program_->release();
}

void DiceRenderer::release()
{
    program_.reset();
    vao_.reset();
    vbo_.reset();

    textures_.clear();
    cube_positions_.clear();
}
